feat: support WebDAV backup#181
Conversation
feat: support WebDAV backup
📝 WalkthroughWalkthroughAdds WebDAV cloud backup/restore and automatic scheduled backups using a new in-page snapshot service; refactors backup/restore to use snapshots; updates UI and styles for cloud backup settings; adds Swedish locale and many translated strings; and expands extension permissions for downloads and optional host access. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Browser UI
participant SnapshotSvc as window.snapshotService
participant Storage as Local Storage / IndexedDB
participant WebDAV as WebDAV Server
rect rgba(100,200,100,0.5)
Note over User,WebDAV: Manual WebDAV Backup
User->>UI: Click "Backup to WebDAV"
UI->>SnapshotSvc: createSnapshot()
SnapshotSvc->>Storage: export localStorage / IndexedDB
Storage-->>SnapshotSvc: snapshot data
SnapshotSvc-->>UI: snapshot object
UI->>WebDAV: PUT snapshot_<ts>.json (with Basic Auth)
WebDAV-->>UI: 200 OK
UI->>WebDAV: PUT snapshot_latest.json (pointer)
WebDAV-->>UI: 200 OK
UI-->>User: show success
end
rect rgba(100,150,200,0.5)
Note over User,WebDAV: Manual WebDAV Restore
User->>UI: Click "Restore Latest"
UI->>WebDAV: GET snapshot_latest.json
WebDAV-->>UI: latest filename
UI->>WebDAV: GET snapshot_<ts>.json
WebDAV-->>UI: snapshot payload
UI->>SnapshotSvc: normalizeSnapshot() + validateSnapshot()
SnapshotSvc-->>UI: validated snapshot
UI->>SnapshotSvc: restoreSnapshot()
SnapshotSvc->>Storage: clear and write localStorage / IndexedDB
Storage-->>SnapshotSvc: complete
SnapshotSvc-->>UI: success
UI->>UI: reload page
end
rect rgba(200,100,100,0.5)
Note over UI,WebDAV: Automatic Scheduled Backup
Note over UI: Timer fires per saved interval
UI->>SnapshotSvc: createSnapshot()
SnapshotSvc-->>UI: snapshot
UI->>UI: ensureHostPermission()
alt permission granted
UI->>WebDAV: PUT files (non-interactive)
WebDAV-->>UI: success
else permission denied
UI-->>User: log/skip upload
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/languages.js (1)
244-256:⚠️ Potential issue | 🟡 MinorRemove unused
cloudPrefixPlaceholderfrom all locale files or implement the missing wiring.All 30 locale files define
cloudPrefixPlaceholder, but it is never referenced in the codebase. There is no input element inindex.htmlfor a cloud prefix configuration, and no entry inplaceholderMapto wire it up. Either remove this dead code from the locales or implement the missing feature (input element + placeholderMap entry).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/languages.js` around lines 244 - 256, Locale key cloudPrefixPlaceholder is defined but unused; either remove it from all locale files or wire it into the UI by adding a corresponding input in index.html and mapping it in scripts/languages.js's placeholderMap. To implement the feature: add an input element for the cloud prefix in index.html with an id (e.g., "cloudPrefixInput"), then add { id: "cloudPrefixInput", key: "cloudPrefixPlaceholder" } to the placeholderMap array in scripts/languages.js so loadPlaceholders/getPlaceholder logic applies; alternatively, delete the cloudPrefixPlaceholder entries from all locale JSONs if the feature is not needed.
🧹 Nitpick comments (8)
CHANGELOG.md (1)
21-21: Consider adding author attribution and PR link.Every other entry under
[Unreleased] → Added(and prior releases) includes([@author])and([#PR])references. This new entry omits both.✏️ Suggested edit
-- Added WebDAV backup and restore support, including manual cloud backup and restore for MYNT settings. +- Added WebDAV backup and restore support, including manual cloud backup and restore for MYNT settings ([`@opoojkk`](https://github.com/opoojkk)) ([`#181`](https://github.com/prem-k-r/MaterialYouNewTab/pull/181))Also, the PR description mentions automatic scheduled backups support, which is not reflected in this changelog entry.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` at line 21, Update the CHANGELOG entry that currently reads "Added WebDAV backup and restore support, including manual cloud backup and restore for MYNT settings." to include the author and PR attribution in the same format as other entries (e.g., "([`@author`]) ([`#PR`])") and extend the text to mention automatic scheduled backups support (e.g., "...including manual and automatic scheduled cloud backups and restore for MYNT settings."), keeping punctuation and style consistent with other [Unreleased] → Added items.manifest.json (1)
8-15: Redundant specific host entries underoptional_host_permissions.With
http://*/*andhttps://*/*now declared, the specific endpoints on lines 11–14 (google.com/complete/search,duckduckgo.com/ac,search.brave.com/api/suggest,*.wikipedia.org/...) are strict subsets and have no additional effect. They can be removed to simplify the manifest, or — preferably — the wildcards should be removed and the WebDAV host added viapermissions.request({ origins: [...] })at runtime from the user-supplied URL (whichensureHostPermissioninscripts/backup-restore.js:332-352already supports), avoiding a full-web optional grant.Up to you whether to prioritize simplicity (keep wildcards, drop specifics) or minimal permissions (drop wildcards, keep specifics and request WebDAV origin dynamically).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@manifest.json` around lines 8 - 15, The manifest currently declares both broad wildcards in optional_host_permissions and redundant specific origins (the google/duckduckgo/brave/wikipedia endpoints); choose one approach and update optional_host_permissions accordingly: either keep the wildcards (http://*/* and https://*/*) and remove the specific endpoint entries to simplify the manifest, or remove the global wildcards and keep only the necessary specific origins and handle the WebDAV origin at runtime by calling permissions.request({ origins: [...] }) via the existing ensureHostPermission logic in scripts/backup-restore.js so you don't require full-web optional grants.index.html (2)
1897-1898: No upper bound on interval input.
min="1"but nomax, so users can enter enormous values (e.g., millions of hours). Consider a reasonable cap (e.g.,max="720"for 30 days) to prevent accidental scheduling of effectively-never backups.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@index.html` around lines 1897 - 1898, The number input with id "cloudIntervalInput" (class "webdavInput webdavIntervalInput") currently has min="1" but no upper bound; add a sensible max (e.g., max="720") to the input and enforce the same limit in any client-side validation logic that reads/sets this field (validate in the form submit handler or the function that processes cloudIntervalInput to clamp or reject values > max), and update any placeholder/help text if necessary to reflect the allowed range.
1871-1872: Addautocompletehints to credential inputs.The password input has no
autocompleteattribute; addautocomplete="current-password"(andautocomplete="username"on the username field) so browsers and password managers behave correctly, orautocomplete="off"if you explicitly don't want them saved for this WebDAV-server config.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@index.html` around lines 1871 - 1872, The password input with id "webdavPasswordInput" is missing an autocomplete hint; add autocomplete="current-password" to that element and also add autocomplete="username" to the corresponding username input (the WebDAV username input element, e.g., the element with id like "webdavUsernameInput" or the form's username input) so browsers and password managers can store/fill credentials correctly (or use autocomplete="off" on both if you intentionally want to disable saving).locales/fa.js (1)
224-243: Minor: property-key quoting style inconsistent with rest of file.The rest of this file uses unquoted property names (e.g.,
github:,feedback:), but the new keys are quoted ("cloudBackupSectionTitle":). Functionally identical, but consider matching the existing style for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/fa.js` around lines 224 - 243, Change the newly added quoted object keys to the unquoted style used elsewhere in the file: replace "cloudBackupSectionTitle", "webdavTitle", "webdavBackupText", "webdavRestoreText", "webdavUrlPlaceholder", "cloudPrefixPlaceholder", "webdavUsernamePlaceholder", "webdavPasswordPlaceholder", "cloudAutoBackupText", "cloudIntervalPlaceholder", "cloudIntervalHours", "saveBackupConfig", "backupConfigSaved", "webdavMissingConfig", "webdavPermissionDenied", "webdavBackupCompleted", "webdavDownloadFailed", "webdavUploadFailed", "webdavRestoreConfirm", and "webdavLatestPointerMissing" so they appear without surrounding quotes (e.g., cloudBackupSectionTitle:) and preserve the exact string values and commas to keep the object valid.scripts/snapshot-service.js (2)
196-212:openDatabaserejects with a generic message, losing diagnostic detail.
request.onerror = () => reject(new Error("Database error."))discardsrequest.error, which in turn is what bubbles up to the user viawebdavUploadFailed/restorefailed. Forwarding the underlying error gives much better debugging info:- request.onerror = () => reject(new Error("Database error.")); + request.onerror = () => reject(request.error || new Error("Database error."));Also consider handling
request.onblocked(another state that can stall the upgrade path).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/snapshot-service.js` around lines 196 - 212, The openDatabase promise currently rejects with a generic Error in request.onerror, losing diagnostic info; change request.onerror to reject with the underlying IndexedDB error (use request.error or event.target.error) so callers (e.g., webdavUploadFailed/restorefailed) get the real cause, and add a request.onblocked handler that rejects or rejects with a specific "blocked" Error (or logs and rejects) to surface upgrade blocking conditions; locate these handlers in the openDatabase function (request.onerror, request.onblocked) and update them accordingly.
214-221: Redundant condition inisTransientLocalStorageKey.
key.startsWith("quotes_")already matches"quotes_metadata_timestamp", so the explicit equality check is dead code.return key.startsWith("quotes_") - || key === "quotes_metadata_timestamp" || key === "weatherParsedData"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/snapshot-service.js` around lines 214 - 221, isTransientLocalStorageKey contains a redundant check: key.startsWith("quotes_") already covers "quotes_metadata_timestamp", so remove the explicit equality branch (key === "quotes_metadata_timestamp") from the isTransientLocalStorageKey function and keep the other explicit weather-related keys unchanged; update any surrounding comments or tests if they assumed a separate equality check.scripts/backup-restore.js (1)
143-152:cloudLastAutoBackupAtis written twice per successful automatic backup.
uploadSnapshotToWebDAValready setsSTORAGE_KEYS.cloudLastAutoBackupAtat line 149 whenisAutomaticis true, and thenmaybeRunAutomaticBackupsets it again at line 315. Harmless, but redundant — drop one of them (probably the one inmaybeRunAutomaticBackup, so the timestamp reflects upload completion consistently).Also applies to: 302-317
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/backup-restore.js` around lines 143 - 152, Remove the redundant write to STORAGE_KEYS.cloudLastAutoBackupAt so the timestamp is only set when the upload actually completes; specifically, keep the setLocalStorage call inside uploadSnapshotToWebDAV (which runs when isAutomatic is true) and remove the duplicate localStorage.setItem call in maybeRunAutomaticBackup (the block that currently sets cloudLastAutoBackupAt around lines 302–317), ensuring only uploadSnapshotToWebDAV updates the key to reflect successful upload completion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@index.html`:
- Around line 1867-1872: The WebDAV inputs (webdavUrlInput, webdavUsernameInput,
webdavPasswordInput) lack accessible labels; add <label> elements associated to
each input (using for="webdavUrlInput", for="webdavUsernameInput",
for="webdavPasswordInput") and use the same visible-or-visually-hidden label
pattern as editBookmarkName/editBookmarkURL so screen readers get proper labels
while preserving the existing placeholders; ensure the labels provide
descriptive text (e.g., "WebDAV URL", "Username (optional)", "Password or app
password (optional)").
- Line 1859: The Cloud Backup UI fails to translate because
scripts/backup-restore.js uses non-matching keys: update the assignment for
element id "#cloudBackupSectionTitle" in scripts/backup-restore.js (where
t("backupModuleTitle") is used) to t("cloudBackupSectionTitle"), and replace
t("cloudAutoBackupHint") with an existing locale key or add a new
"cloudAutoBackupHint" entry to all locale files (locales/*.js) so keys are
consistent; also open locales/en.js and remove the duplicated webdav entries
(keep a single set for webdavTitle, webdavBackupText, webdavRestoreText,
webdavUrlPlaceholder) ensuring the retained webdavRestoreText value is the
intended one across all locales.
In `@locales/el.js`:
- Around line 198-217: Add the missing "cloudAutoBackupHint" and
"cloudAutoBackupInvalidInterval" keys to the locales/el.js translations (the
same change should be applied to the other locale files noted), providing Greek
strings for the auto-backup toggle description and the alert when the interval
is below 1 hour; update the same translation object where "cloudAutoBackupText"
and related webdav keys live so the keys are present when
scripts/backup-restore.js looks them up.
In `@locales/en.js`:
- Around line 208-243: Remove the stale duplicate WebDAV entries (the earlier
block defining webdavTitle, webdavBackupText, webdavRestoreText,
webdavUrlPlaceholder, webdavUsernamePlaceholder, webdavPasswordPlaceholder,
webdavMissingConfig, webdavPermissionDenied, webdavBackupCompleted,
webdavDownloadFailed, webdavUploadFailed, webdavRestoreConfirm and the obsolete
webdavFileNamePlaceholder) so only the correct cloudBackupSection block remains;
then add the three missing locale keys used by scripts/backup-restore.js —
backupModuleTitle, cloudAutoBackupHint, and cloudAutoBackupInvalidInterval — (or
alternatively change the t(...) calls in backup-restore.js/applyStorageTexts()
to use the existing cloudBackupSectionTitle, cloudAutoBackupText and
cloudIntervalInvalid key names), ensuring the keys referenced in
applyStorageTexts() and by the UI elements (`#cloudBackupSectionTitle`,
`#cloudAutoBackupHint` and the invalid interval alert) match the locale file.
In `@manifest`(firefox).json:
- Around line 15-18: Remove the broad "http://*/*" and "https://*/*" entries
from the manifest's optional_permissions array and instead request precise
origins at runtime: keep only genuinely static optional permissions in
optional_permissions, and when the user supplies a WebDAV URL construct a
specific origin pattern (e.g., "https://example.com/*") and call
browser.permissions.request({origins: [constructedOrigin]}) to obtain access;
update any code that assumes the wildcard (search for optional_permissions and
any permissions.request usage) to build and request the specific origin from the
user-supplied URL before attempting WebDAV operations.
In `@scripts/backup-restore.js`:
- Around line 257-261: The code is persisting the WebDAV password in plaintext
via localStorage.setItem(STORAGE_KEYS.webdavPassword,
webdavPasswordInput.value); change this so credentials are not page-accessible:
when persisting, use chrome.storage.local (extension-isolated) instead of
localStorage for STORAGE_KEYS.webdavPassword and related WebDAV fields, and if
there's a "remember" toggle implement session-only storage (sessionStorage) when
not remembered; update any save logic around webdavUsernameInput.value,
webdavPasswordInput.value and cloudAutoBackupCheckbox to use the appropriate
storage API and ensure retrieval code reads from
chrome.storage.local/sessionStorage accordingly, and add a brief UI notice near
the WebDAV input warning users about the risk and recommending an app password.
- Around line 267-279: getWebDAVConfig currently calls new URL(baseUrl).origin
which can throw for malformed user input and surface as an upload failure;
modify getWebDAVConfig to validate/parse baseUrl inside a try/catch (or use a
safe absolute-URL check) and if parsing fails return null so callers (e.g.,
uploadSnapshotToWebDAV) treat it as missing config; ensure the returned object
still contains baseUrl, username, password and a safe requestOrigin only when
URL parsing succeeds.
- Around line 217-230: The translations use nonexistent locale keys
(backupModuleTitle, cloudAutoBackupHint, cloudAutoBackupInvalidInterval) causing
silent fallbacks; update the calls in applyStorageTexts (and the
saveBackupConfig usage) to the existing locale keys defined in your locales (or
alternatively add these three keys to every locales/*.js), i.e., change
t("backupModuleTitle") and t("cloudAutoBackupHint") in applyStorageTexts and
t("cloudAutoBackupInvalidInterval") in saveBackupConfig to match the established
keys in en.js so the UI respects the selected language.
In `@scripts/snapshot-service.js`:
- Around line 119-151: The exportObjectStore function can hang because
store.getAllKeys() and store.get(key) lack error handlers; add request.onerror
handlers for the getAllKeys() request to reject with its error, and for each
store.get(key) to reject on failure (referencing exportObjectStore,
store.getAllKeys, and the inner store.get loop), ensuring pending is
decremented/short-circuited appropriately if needed; keep the existing
transaction.onerror but ensure any rejection clears outstanding callbacks so the
returned Promise always resolves or rejects, and still await
serializeIndexedDBValue(result) inside the successful get handler.
In `@style.css`:
- Around line 4073-4079: The .cloudMethodInlinePicker rule uses an undefined CSS
variable --lightColor-blue so its background becomes transparent; update the
background-color to a valid token (e.g. --bg-color-blue or
--accentLightTint-blue) or remove the background-color declaration if no fill is
desired; modify the .cloudMethodInlinePicker selector in the stylesheet to
reference the chosen existing variable (instead of --lightColor-blue) and verify
the visual result in the UI.
---
Outside diff comments:
In `@scripts/languages.js`:
- Around line 244-256: Locale key cloudPrefixPlaceholder is defined but unused;
either remove it from all locale files or wire it into the UI by adding a
corresponding input in index.html and mapping it in scripts/languages.js's
placeholderMap. To implement the feature: add an input element for the cloud
prefix in index.html with an id (e.g., "cloudPrefixInput"), then add { id:
"cloudPrefixInput", key: "cloudPrefixPlaceholder" } to the placeholderMap array
in scripts/languages.js so loadPlaceholders/getPlaceholder logic applies;
alternatively, delete the cloudPrefixPlaceholder entries from all locale JSONs
if the feature is not needed.
---
Nitpick comments:
In `@CHANGELOG.md`:
- Line 21: Update the CHANGELOG entry that currently reads "Added WebDAV backup
and restore support, including manual cloud backup and restore for MYNT
settings." to include the author and PR attribution in the same format as other
entries (e.g., "([`@author`]) ([`#PR`])") and extend the text to mention automatic
scheduled backups support (e.g., "...including manual and automatic scheduled
cloud backups and restore for MYNT settings."), keeping punctuation and style
consistent with other [Unreleased] → Added items.
In `@index.html`:
- Around line 1897-1898: The number input with id "cloudIntervalInput" (class
"webdavInput webdavIntervalInput") currently has min="1" but no upper bound; add
a sensible max (e.g., max="720") to the input and enforce the same limit in any
client-side validation logic that reads/sets this field (validate in the form
submit handler or the function that processes cloudIntervalInput to clamp or
reject values > max), and update any placeholder/help text if necessary to
reflect the allowed range.
- Around line 1871-1872: The password input with id "webdavPasswordInput" is
missing an autocomplete hint; add autocomplete="current-password" to that
element and also add autocomplete="username" to the corresponding username input
(the WebDAV username input element, e.g., the element with id like
"webdavUsernameInput" or the form's username input) so browsers and password
managers can store/fill credentials correctly (or use autocomplete="off" on both
if you intentionally want to disable saving).
In `@locales/fa.js`:
- Around line 224-243: Change the newly added quoted object keys to the unquoted
style used elsewhere in the file: replace "cloudBackupSectionTitle",
"webdavTitle", "webdavBackupText", "webdavRestoreText", "webdavUrlPlaceholder",
"cloudPrefixPlaceholder", "webdavUsernamePlaceholder",
"webdavPasswordPlaceholder", "cloudAutoBackupText", "cloudIntervalPlaceholder",
"cloudIntervalHours", "saveBackupConfig", "backupConfigSaved",
"webdavMissingConfig", "webdavPermissionDenied", "webdavBackupCompleted",
"webdavDownloadFailed", "webdavUploadFailed", "webdavRestoreConfirm", and
"webdavLatestPointerMissing" so they appear without surrounding quotes (e.g.,
cloudBackupSectionTitle:) and preserve the exact string values and commas to
keep the object valid.
In `@manifest.json`:
- Around line 8-15: The manifest currently declares both broad wildcards in
optional_host_permissions and redundant specific origins (the
google/duckduckgo/brave/wikipedia endpoints); choose one approach and update
optional_host_permissions accordingly: either keep the wildcards (http://*/* and
https://*/*) and remove the specific endpoint entries to simplify the manifest,
or remove the global wildcards and keep only the necessary specific origins and
handle the WebDAV origin at runtime by calling permissions.request({ origins:
[...] }) via the existing ensureHostPermission logic in
scripts/backup-restore.js so you don't require full-web optional grants.
In `@scripts/backup-restore.js`:
- Around line 143-152: Remove the redundant write to
STORAGE_KEYS.cloudLastAutoBackupAt so the timestamp is only set when the upload
actually completes; specifically, keep the setLocalStorage call inside
uploadSnapshotToWebDAV (which runs when isAutomatic is true) and remove the
duplicate localStorage.setItem call in maybeRunAutomaticBackup (the block that
currently sets cloudLastAutoBackupAt around lines 302–317), ensuring only
uploadSnapshotToWebDAV updates the key to reflect successful upload completion.
In `@scripts/snapshot-service.js`:
- Around line 196-212: The openDatabase promise currently rejects with a generic
Error in request.onerror, losing diagnostic info; change request.onerror to
reject with the underlying IndexedDB error (use request.error or
event.target.error) so callers (e.g., webdavUploadFailed/restorefailed) get the
real cause, and add a request.onblocked handler that rejects or rejects with a
specific "blocked" Error (or logs and rejects) to surface upgrade blocking
conditions; locate these handlers in the openDatabase function (request.onerror,
request.onblocked) and update them accordingly.
- Around line 214-221: isTransientLocalStorageKey contains a redundant check:
key.startsWith("quotes_") already covers "quotes_metadata_timestamp", so remove
the explicit equality branch (key === "quotes_metadata_timestamp") from the
isTransientLocalStorageKey function and keep the other explicit weather-related
keys unchanged; update any surrounding comments or tests if they assumed a
separate equality check.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5250ce54-5d3d-42f4-b3ee-0bf6fd0626e6
📒 Files selected for processing (39)
CHANGELOG.mdindex.htmllocales/ar_SA.jslocales/az.jslocales/bn.jslocales/cs.jslocales/de.jslocales/el.jslocales/en.jslocales/es.jslocales/fa.jslocales/fr.jslocales/hi.jslocales/hu.jslocales/idn.jslocales/it.jslocales/ja.jslocales/ko.jslocales/mr.jslocales/np.jslocales/pl.jslocales/pt.jslocales/ru.jslocales/sl.jslocales/ta.jslocales/th.jslocales/tr.jslocales/uk.jslocales/ur.jslocales/uz.jslocales/vi.jslocales/zh.jslocales/zh_TW.jsmanifest(firefox).jsonmanifest.jsonscripts/backup-restore.jsscripts/languages.jsscripts/snapshot-service.jsstyle.css
| <div class="section expanded"> | ||
| <div class="sectionOuter"> | ||
| <div class="sectionHeader" role="button"> | ||
| <span class="sectionTitle" id="cloudBackupSectionTitle">Cloud Backup</span> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the missing/mismatched keys
rg -nP '\b(backupModuleTitle|cloudAutoBackupHint|cloudBackupSectionTitle)\b' --type=js --type=html
echo "---"
# Show en.js duplicates for webdav* keys
rg -nP '"(webdavTitle|webdavBackupText|webdavRestoreText|webdavUrlPlaceholder)"\s*:' locales/en.jsRepository: prem-k-r/MaterialYouNewTab
Length of output: 3406
Translation key mismatches prevent Cloud Backup section from displaying properly.
The code in scripts/backup-restore.js (lines 218 and 223) attempts to translate elements using keys that either don't exist or don't match the locale files:
-
Line 218 sets
#cloudBackupSectionTitleusingt("backupModuleTitle"), butbackupModuleTitleis not defined in any locale file. All 28 locale files definecloudBackupSectionTitleinstead. -
Line 223 sets
#cloudAutoBackupHintusingt("cloudAutoBackupHint"), but this key is missing from all locale files entirely.
Additionally, locales/en.js contains duplicate entries for WebDAV keys:
- Lines 208–211 define webdavTitle, webdavBackupText, webdavRestoreText ("Restore from WebDAV"), and webdavUrlPlaceholder
- Lines 225–228 redefine the same keys with conflicting
webdavRestoreTextvalue ("Restore Latest")
Align the translation key names between scripts/backup-restore.js and all locale files (locales/*.js). Either:
- Change line 218 to use
t("cloudBackupSectionTitle")and line 223 to use an existing key, or - Add
backupModuleTitleandcloudAutoBackupHintdefinitions to all 28 locale files
Remove duplicate webdav entries in locales/en.js and keep one consistent set.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@index.html` at line 1859, The Cloud Backup UI fails to translate because
scripts/backup-restore.js uses non-matching keys: update the assignment for
element id "#cloudBackupSectionTitle" in scripts/backup-restore.js (where
t("backupModuleTitle") is used) to t("cloudBackupSectionTitle"), and replace
t("cloudAutoBackupHint") with an existing locale key or add a new
"cloudAutoBackupHint" entry to all locale files (locales/*.js) so keys are
consistent; also open locales/en.js and remove the duplicated webdav entries
(keep a single set for webdavTitle, webdavBackupText, webdavRestoreText,
webdavUrlPlaceholder) ensuring the retained webdavRestoreText value is the
intended one across all locales.
| <input type="url" id="webdavUrlInput" class="webdavInput" | ||
| placeholder="https://example.com/remote.php/dav/files/username/backups"> | ||
| <input type="text" id="webdavUsernameInput" class="webdavInput" | ||
| placeholder="Username (optional)"> | ||
| <input type="password" id="webdavPasswordInput" class="webdavInput" | ||
| placeholder="Password or app password (optional)"> |
There was a problem hiding this comment.
Missing <label> elements for WebDAV inputs.
The URL, username, and password fields rely solely on placeholder text. Placeholders aren't accessible labels — screen readers may not announce them and the fields have no visible label text after the user starts typing. Consider adding visually-associated (or visually-hidden but programmatically-associated) <label> elements, matching the pattern used for editBookmarkName/editBookmarkURL elsewhere in this file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@index.html` around lines 1867 - 1872, The WebDAV inputs (webdavUrlInput,
webdavUsernameInput, webdavPasswordInput) lack accessible labels; add <label>
elements associated to each input (using for="webdavUrlInput",
for="webdavUsernameInput", for="webdavPasswordInput") and use the same
visible-or-visually-hidden label pattern as editBookmarkName/editBookmarkURL so
screen readers get proper labels while preserving the existing placeholders;
ensure the labels provide descriptive text (e.g., "WebDAV URL", "Username
(optional)", "Password or app password (optional)").
| "cloudBackupSectionTitle": "Αντίγραφο ασφαλείας cloud", | ||
| "webdavTitle": "Συγχρονισμός WebDAV", | ||
| "webdavBackupText": "Δημιουργία αντιγράφου στο WebDAV", | ||
| "webdavRestoreText": "Επαναφορά τελευταίου", | ||
| "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", | ||
| "cloudPrefixPlaceholder": "MYNT_Backup", | ||
| "webdavUsernamePlaceholder": "Όνομα χρήστη (προαιρετικό)", | ||
| "webdavPasswordPlaceholder": "Κωδικός ή κωδικός εφαρμογής (προαιρετικό)", | ||
| "cloudAutoBackupText": "Ενεργοποίηση αυτόματου αντιγράφου ασφαλείας", | ||
| "cloudIntervalPlaceholder": "Ώρες", | ||
| "cloudIntervalHours": "Ώρες", | ||
| "saveBackupConfig": "Αποθήκευση ρυθμίσεων αντιγράφου ασφαλείας", | ||
| "backupConfigSaved": "Οι ρυθμίσεις αντιγράφου ασφαλείας αποθηκεύτηκαν.", | ||
| "webdavMissingConfig": "Συμπληρώστε πρώτα το URL του φακέλου WebDAV.", | ||
| "webdavPermissionDenied": "Δεν δόθηκε άδεια host για αυτόν τον διακομιστή WebDAV.", | ||
| "webdavBackupCompleted": "Το αντίγραφο ασφαλείας μεταφορτώθηκε επιτυχώς στο WebDAV!", | ||
| "webdavDownloadFailed": "Αποτυχία λήψης αντιγράφου ασφαλείας από το WebDAV: ", | ||
| "webdavUploadFailed": "Αποτυχία μεταφόρτωσης αντιγράφου ασφαλείας στο WebDAV: ", | ||
| "webdavRestoreConfirm": "Επαναφορά ρυθμίσεων από το πιο πρόσφατο αντίγραφο ασφαλείας WebDAV; Τα τρέχοντα τοπικά δεδομένα θα αντικατασταθούν.", | ||
| "webdavLatestPointerMissing": "Δεν βρέθηκε εγγραφή του πιο πρόσφατου αντιγράφου ασφαλείας στον φάκελο MYNT." |
There was a problem hiding this comment.
Missing cloudAutoBackupHint and cloudAutoBackupInvalidInterval translations.
These two keys are surfaced to users by scripts/backup-restore.js (the auto-backup toggle description and the alert when the interval is below 1 hour), but they’re not present in this locale. Greek users will see the English fallback for both. Please add Greek equivalents — same applies to locales/ru.js, locales/np.js, and the other 23 locale files touched by this PR.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@locales/el.js` around lines 198 - 217, Add the missing "cloudAutoBackupHint"
and "cloudAutoBackupInvalidInterval" keys to the locales/el.js translations (the
same change should be applied to the other locale files noted), providing Greek
strings for the auto-backup toggle description and the alert when the interval
is below 1 hour; update the same translation object where "cloudAutoBackupText"
and related webdav keys live so the keys are present when
scripts/backup-restore.js looks them up.
| "webdavTitle": "WebDAV Sync", | ||
| "webdavBackupText": "Backup to WebDAV", | ||
| "webdavRestoreText": "Restore from WebDAV", | ||
| "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", | ||
| "webdavFileNamePlaceholder": "mynt-backup.json", | ||
| "webdavUsernamePlaceholder": "Username (optional)", | ||
| "webdavPasswordPlaceholder": "Password or app password (optional)", | ||
| "webdavMissingConfig": "Please fill in the WebDAV URL and file name first.", | ||
| "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", | ||
| "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", | ||
| "webdavDownloadFailed": "Failed to download backup from WebDAV: ", | ||
| "webdavUploadFailed": "Failed to upload backup to WebDAV: ", | ||
| "webdavRestoreConfirm": "Restore settings from WebDAV? Your current local data will be overwritten.", | ||
| "deleteBookmark": "Are you sure you want to delete the bookmark \"{title}\"?", // Do not translate {title} | ||
| "UnsupportedBrowser": "Bookmarks are not supported in your browser.", | ||
| "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?" | ||
| }; | ||
| "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?", | ||
| "cloudBackupSectionTitle": "Cloud Backup", | ||
| "webdavTitle": "WebDAV Sync", | ||
| "webdavBackupText": "Backup to WebDAV", | ||
| "webdavRestoreText": "Restore Latest", | ||
| "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", | ||
| "cloudPrefixPlaceholder": "MYNT_Backup", | ||
| "webdavUsernamePlaceholder": "Username (optional)", | ||
| "webdavPasswordPlaceholder": "Password or app password (optional)", | ||
| "cloudAutoBackupText": "Enable automatic backup", | ||
| "cloudIntervalPlaceholder": "Hours", | ||
| "cloudIntervalHours": "Hours", | ||
| "saveBackupConfig": "Save Backup Config", | ||
| "backupConfigSaved": "Backup configuration saved.", | ||
| "webdavMissingConfig": "Please fill in the WebDAV folder URL first.", | ||
| "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", | ||
| "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", | ||
| "webdavDownloadFailed": "Failed to download backup from WebDAV: ", | ||
| "webdavUploadFailed": "Failed to upload backup to WebDAV: ", | ||
| "webdavRestoreConfirm": "Restore settings from the latest WebDAV backup? Your current local data will be overwritten.", | ||
| "webdavLatestPointerMissing": "No latest backup record was found in the MYNT folder." |
There was a problem hiding this comment.
Remove duplicate WebDAV keys and add the missing translation entries.
Biome flags that webdavTitle, webdavBackupText, webdavRestoreText, webdavUrlPlaceholder, webdavUsernamePlaceholder, webdavPasswordPlaceholder, webdavMissingConfig, webdavPermissionDenied, webdavBackupCompleted, webdavDownloadFailed, webdavUploadFailed, and webdavRestoreConfirm are all defined twice (lines 208‑220 are dead, overwritten at 225‑242). The stale block also keeps obsolete keys webdavFileNamePlaceholder that no longer correspond to any UI element.
Additionally, scripts/backup-restore.js references three translation keys that are not defined anywhere in the locales (only in storageFallbackTexts):
backupModuleTitle— applied to#cloudBackupSectionTitleatapplyStorageTexts(). The locale key you actually defined here iscloudBackupSectionTitle, so this will always fall back to the English default even in other languages.cloudAutoBackupHint— applied to#cloudAutoBackupHint.cloudAutoBackupInvalidInterval— shown viaalertPromptwhen the auto-backup interval is invalid.
Either rename the t(...) calls in backup-restore.js to match the existing locale keys, or add these three keys to en.js (and to every other locale file). Right now three user-facing strings will never localize, which defeats the purpose of the locale updates in this PR.
🔧 Proposed cleanup
- "webdavTitle": "WebDAV Sync",
- "webdavBackupText": "Backup to WebDAV",
- "webdavRestoreText": "Restore from WebDAV",
- "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups",
- "webdavFileNamePlaceholder": "mynt-backup.json",
- "webdavUsernamePlaceholder": "Username (optional)",
- "webdavPasswordPlaceholder": "Password or app password (optional)",
- "webdavMissingConfig": "Please fill in the WebDAV URL and file name first.",
- "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.",
- "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!",
- "webdavDownloadFailed": "Failed to download backup from WebDAV: ",
- "webdavUploadFailed": "Failed to upload backup to WebDAV: ",
- "webdavRestoreConfirm": "Restore settings from WebDAV? Your current local data will be overwritten.",
"deleteBookmark": "Are you sure you want to delete the bookmark \"{title}\"?",
"UnsupportedBrowser": "Bookmarks are not supported in your browser.",
"resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?",
"cloudBackupSectionTitle": "Cloud Backup",
+ "cloudAutoBackupHint": "Back up settings to WebDAV on a schedule",
+ "cloudAutoBackupInvalidInterval": "Automatic backup interval must be at least 1 hour.",
"webdavTitle": "WebDAV Sync",…and in scripts/backup-restore.js:
- document.getElementById("cloudBackupSectionTitle").textContent = t("backupModuleTitle");
+ document.getElementById("cloudBackupSectionTitle").textContent = t("cloudBackupSectionTitle");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "webdavTitle": "WebDAV Sync", | |
| "webdavBackupText": "Backup to WebDAV", | |
| "webdavRestoreText": "Restore from WebDAV", | |
| "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", | |
| "webdavFileNamePlaceholder": "mynt-backup.json", | |
| "webdavUsernamePlaceholder": "Username (optional)", | |
| "webdavPasswordPlaceholder": "Password or app password (optional)", | |
| "webdavMissingConfig": "Please fill in the WebDAV URL and file name first.", | |
| "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", | |
| "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", | |
| "webdavDownloadFailed": "Failed to download backup from WebDAV: ", | |
| "webdavUploadFailed": "Failed to upload backup to WebDAV: ", | |
| "webdavRestoreConfirm": "Restore settings from WebDAV? Your current local data will be overwritten.", | |
| "deleteBookmark": "Are you sure you want to delete the bookmark \"{title}\"?", // Do not translate {title} | |
| "UnsupportedBrowser": "Bookmarks are not supported in your browser.", | |
| "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?" | |
| }; | |
| "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?", | |
| "cloudBackupSectionTitle": "Cloud Backup", | |
| "webdavTitle": "WebDAV Sync", | |
| "webdavBackupText": "Backup to WebDAV", | |
| "webdavRestoreText": "Restore Latest", | |
| "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", | |
| "cloudPrefixPlaceholder": "MYNT_Backup", | |
| "webdavUsernamePlaceholder": "Username (optional)", | |
| "webdavPasswordPlaceholder": "Password or app password (optional)", | |
| "cloudAutoBackupText": "Enable automatic backup", | |
| "cloudIntervalPlaceholder": "Hours", | |
| "cloudIntervalHours": "Hours", | |
| "saveBackupConfig": "Save Backup Config", | |
| "backupConfigSaved": "Backup configuration saved.", | |
| "webdavMissingConfig": "Please fill in the WebDAV folder URL first.", | |
| "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", | |
| "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", | |
| "webdavDownloadFailed": "Failed to download backup from WebDAV: ", | |
| "webdavUploadFailed": "Failed to upload backup to WebDAV: ", | |
| "webdavRestoreConfirm": "Restore settings from the latest WebDAV backup? Your current local data will be overwritten.", | |
| "webdavLatestPointerMissing": "No latest backup record was found in the MYNT folder." | |
| "deleteBookmark": "Are you sure you want to delete the bookmark \"{title}\"?", // Do not translate {title} | |
| "UnsupportedBrowser": "Bookmarks are not supported in your browser.", | |
| "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?", | |
| "cloudBackupSectionTitle": "Cloud Backup", | |
| "cloudAutoBackupHint": "Back up settings to WebDAV on a schedule", | |
| "cloudAutoBackupInvalidInterval": "Automatic backup interval must be at least 1 hour.", | |
| "webdavTitle": "WebDAV Sync", | |
| "webdavBackupText": "Backup to WebDAV", | |
| "webdavRestoreText": "Restore Latest", | |
| "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", | |
| "cloudPrefixPlaceholder": "MYNT_Backup", | |
| "webdavUsernamePlaceholder": "Username (optional)", | |
| "webdavPasswordPlaceholder": "Password or app password (optional)", | |
| "cloudAutoBackupText": "Enable automatic backup", | |
| "cloudIntervalPlaceholder": "Hours", | |
| "cloudIntervalHours": "Hours", | |
| "saveBackupConfig": "Save Backup Config", | |
| "backupConfigSaved": "Backup configuration saved.", | |
| "webdavMissingConfig": "Please fill in the WebDAV folder URL first.", | |
| "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", | |
| "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", | |
| "webdavDownloadFailed": "Failed to download backup from WebDAV: ", | |
| "webdavUploadFailed": "Failed to upload backup to WebDAV: ", | |
| "webdavRestoreConfirm": "Restore settings from the latest WebDAV backup? Your current local data will be overwritten.", | |
| "webdavLatestPointerMissing": "No latest backup record was found in the MYNT folder." |
🧰 Tools
🪛 Biome (2.4.11)
[error] 208-208: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 209-209: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 210-210: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 211-211: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 213-213: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 214-214: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 215-215: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 216-216: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 217-217: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 218-218: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 219-219: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
[error] 220-220: This property is later overwritten by an object member with the same name.
(lint/suspicious/noDuplicateObjectKeys)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@locales/en.js` around lines 208 - 243, Remove the stale duplicate WebDAV
entries (the earlier block defining webdavTitle, webdavBackupText,
webdavRestoreText, webdavUrlPlaceholder, webdavUsernamePlaceholder,
webdavPasswordPlaceholder, webdavMissingConfig, webdavPermissionDenied,
webdavBackupCompleted, webdavDownloadFailed, webdavUploadFailed,
webdavRestoreConfirm and the obsolete webdavFileNamePlaceholder) so only the
correct cloudBackupSection block remains; then add the three missing locale keys
used by scripts/backup-restore.js — backupModuleTitle, cloudAutoBackupHint, and
cloudAutoBackupInvalidInterval — (or alternatively change the t(...) calls in
backup-restore.js/applyStorageTexts() to use the existing
cloudBackupSectionTitle, cloudAutoBackupText and cloudIntervalInvalid key
names), ensuring the keys referenced in applyStorageTexts() and by the UI
elements (`#cloudBackupSectionTitle`, `#cloudAutoBackupHint` and the invalid
interval alert) match the locale file.
| "optional_permissions": [ | ||
| "http://*/*", | ||
| "https://*/*" | ||
| ], |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Firefox AMO review policy optional_permissions http://*/* wildcard host permissions user-provided URLs
💡 Result:
Firefox AMO review policy requires add-ons to "only request those permissions that are necessary for them to function" (Mozilla Add-on Policies). Broad host permissions like http:/// (equivalent to <all_urls>) are permitted if justified by the extension's core functionality, but reviewers scrutinize them closely during human review to ensure minimal necessary scope. Optional permissions (via optional_permissions in Manifest V2 or optional_host_permissions in V3+) allow requesting host permissions like http:/// at runtime using browser.permissions.request({origins: ['<all_urls>']}), prompting users for "Access your data for all websites". Users can manage these in about:addons. Reviewers evaluate if runtime requests align with described functionality and privacy practices; unjustified broad optional permissions risk rejection. For user-provided URLs, extensions should request specific narrow host patterns at runtime (e.g., origins: ['http://example.com/*']) rather than broad wildcards, minimizing scope per policy emphasis on least privilege. No explicit ban on wildcard optional_permissions found, but documentation advises avoiding <all_urls> where possible (e.g., use activeTab).
Citations:
- 1: https://developer.mozilla.org/en-US/Add-ons/AMO/Policy/Reviews
- 2: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/AMO/Policy/Reviews
- 3: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/optional_permissions
- 4: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/permissions
- 5: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/host_permissions
🏁 Script executed:
# Explore repository structure to understand the extension's purpose and context
git ls-files | head -30Repository: prem-k-r/MaterialYouNewTab
Length of output: 664
🏁 Script executed:
# Look for manifest files and documentation
find . -type f \( -name "manifest*.json" -o -name "README*" -o -name "*.md" \) | grep -v node_modulesRepository: prem-k-r/MaterialYouNewTab
Length of output: 251
🏁 Script executed:
# Read the Firefox manifest file to understand the extension
cat -n manifest.firefox.json | head -50Repository: prem-k-r/MaterialYouNewTab
Length of output: 123
🏁 Script executed:
# Search for WebDAV-related code or comments
rg -i "webdav" --max-count 20Repository: prem-k-r/MaterialYouNewTab
Length of output: 40107
🏁 Script executed:
# Look for any AMO review notes or comments about permissions
rg -i "amo|review|permission" --max-count 15Repository: prem-k-r/MaterialYouNewTab
Length of output: 12269
🏁 Script executed:
# Read the Firefox manifest file (with parentheses in filename)
cat -n "manifest(firefox).json"Repository: prem-k-r/MaterialYouNewTab
Length of output: 1362
🏁 Script executed:
# Check README for WebDAV documentation
rg -i "webdav|permission|backup" README.md -A 2 -B 2Repository: prem-k-r/MaterialYouNewTab
Length of output: 632
Remove broad http://*/* + https://*/* from optional_permissions; request narrower origins at runtime instead.
Firefox AMO policy requires minimal permission scope, even for optional permissions. Declaring broad wildcards in the manifest—even when narrowed at runtime via permissions.request()—risks rejection during review because the manifest itself signals overly broad requests to reviewers.
Since WebDAV URLs are user-supplied at runtime, construct specific origins from the user's configured URL and request only those (e.g., origins: ['https://user-domain.com/*']) instead of pre-declaring http://*/* and https://*/*. This approach:
- Demonstrates least-privilege compliance in the manifest itself
- Aligns with Mozilla's guidance for user-provided endpoints
- Avoids needing explicit AMO justification for full-web wildcards
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@manifest`(firefox).json around lines 15 - 18, Remove the broad "http://*/*"
and "https://*/*" entries from the manifest's optional_permissions array and
instead request precise origins at runtime: keep only genuinely static optional
permissions in optional_permissions, and when the user supplies a WebDAV URL
construct a specific origin pattern (e.g., "https://example.com/*") and call
browser.permissions.request({origins: [constructedOrigin]}) to obtain access;
update any code that assumes the wildcard (search for optional_permissions and
any permissions.request usage) to build and request the specific origin from the
user-supplied URL before attempting WebDAV operations.
| function applyStorageTexts() { | ||
| document.getElementById("cloudBackupSectionTitle").textContent = t("backupModuleTitle"); | ||
| document.getElementById("webdavTitle").textContent = t("webdavTitle"); | ||
| document.getElementById("webdavBackupText").textContent = t("webdavBackupText"); | ||
| document.getElementById("webdavRestoreText").textContent = t("webdavRestoreText"); | ||
| document.getElementById("cloudAutoBackupText").textContent = t("cloudAutoBackupText"); | ||
| document.getElementById("cloudAutoBackupHint").textContent = t("cloudAutoBackupHint"); | ||
| document.getElementById("cloudIntervalUnitLabel").textContent = t("cloudIntervalHours"); | ||
| saveBackupConfigBtn.textContent = t("saveBackupConfig"); | ||
| webdavUrlInput.placeholder = t("webdavUrlPlaceholder"); | ||
| webdavUsernameInput.placeholder = t("webdavUsernamePlaceholder"); | ||
| webdavPasswordInput.placeholder = t("webdavPasswordPlaceholder"); | ||
| cloudIntervalInput.placeholder = t("cloudIntervalPlaceholder"); | ||
| } |
There was a problem hiding this comment.
Root cause of the missing-translation bug.
t("backupModuleTitle"), t("cloudAutoBackupHint"), and (in saveBackupConfig) t("cloudAutoBackupInvalidInterval") all resolve to keys that are not defined in any locales/*.js. They silently degrade to the English storageFallbackTexts strings, so changing the extension language has no effect on these three UI strings.
Either (preferred) align the key used here with the locale key already defined in en.js:
- document.getElementById("cloudBackupSectionTitle").textContent = t("backupModuleTitle");
+ document.getElementById("cloudBackupSectionTitle").textContent = t("cloudBackupSectionTitle");…or add backupModuleTitle, cloudAutoBackupHint, and cloudAutoBackupInvalidInterval to every locale file. A single source-of-truth is safer than keeping duplicated fallbacks.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/backup-restore.js` around lines 217 - 230, The translations use
nonexistent locale keys (backupModuleTitle, cloudAutoBackupHint,
cloudAutoBackupInvalidInterval) causing silent fallbacks; update the calls in
applyStorageTexts (and the saveBackupConfig usage) to the existing locale keys
defined in your locales (or alternatively add these three keys to every
locales/*.js), i.e., change t("backupModuleTitle") and t("cloudAutoBackupHint")
in applyStorageTexts and t("cloudAutoBackupInvalidInterval") in saveBackupConfig
to match the established keys in en.js so the UI respects the selected language.
| localStorage.setItem(STORAGE_KEYS.webdavUrl, config.baseUrl); | ||
| localStorage.setItem(STORAGE_KEYS.webdavUsername, webdavUsernameInput.value); | ||
| localStorage.setItem(STORAGE_KEYS.webdavPassword, webdavPasswordInput.value); | ||
| localStorage.setItem(STORAGE_KEYS.cloudAutoEnabled, String(cloudAutoBackupCheckbox.checked)); | ||
| localStorage.setItem(STORAGE_KEYS.cloudIntervalHours, String(intervalHours)); |
There was a problem hiding this comment.
Storing the WebDAV password in plaintext in localStorage is a privacy risk.
localStorage.setItem(STORAGE_KEYS.webdavPassword, webdavPasswordInput.value) persists the WebDAV credentials in clear text. Any script running in the new-tab origin (including compromised extensions sharing the origin, XSS via an injected wallpaper/proxy response, or DevTools snooping by anyone with physical access) can exfiltrate them. App passwords partially mitigate this, but the UI placeholder suggests a plain password is also acceptable.
Consider at minimum:
- Documenting the risk in the WebDAV UI (e.g., strongly recommend an app password).
- Moving credentials to
chrome.storage.local(extension-isolated) instead of page-accessiblelocalStorage. - Optionally, session-only credentials (
sessionStorage) when "remember" is off.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/backup-restore.js` around lines 257 - 261, The code is persisting the
WebDAV password in plaintext via
localStorage.setItem(STORAGE_KEYS.webdavPassword, webdavPasswordInput.value);
change this so credentials are not page-accessible: when persisting, use
chrome.storage.local (extension-isolated) instead of localStorage for
STORAGE_KEYS.webdavPassword and related WebDAV fields, and if there's a
"remember" toggle implement session-only storage (sessionStorage) when not
remembered; update any save logic around webdavUsernameInput.value,
webdavPasswordInput.value and cloudAutoBackupCheckbox to use the appropriate
storage API and ensure retrieval code reads from
chrome.storage.local/sessionStorage accordingly, and add a brief UI notice near
the WebDAV input warning users about the risk and recommending an app password.
| function getWebDAVConfig() { | ||
| const baseUrl = webdavUrlInput.value.trim().replace(/\/+$/, ""); | ||
| if (!baseUrl) { | ||
| return null; | ||
| } | ||
|
|
||
| store.clear(); | ||
| const entries = Object.entries(data); | ||
| let pending = entries.length; | ||
| return { | ||
| baseUrl, | ||
| username: webdavUsernameInput.value, | ||
| password: webdavPasswordInput.value, | ||
| requestOrigin: new URL(baseUrl).origin + "/*" | ||
| }; | ||
| } |
There was a problem hiding this comment.
new URL(baseUrl) can throw for user-entered malformed URLs.
If the user enters something that passes the empty check but isn’t a valid absolute URL (e.g., example.com/dav), new URL(baseUrl).origin throws TypeError: Invalid URL. The error propagates up to uploadSnapshotToWebDAV where it surfaces as webdavUploadFailed: Invalid URL, which is confusing — the real cause is configuration, not an upload failure.
Consider wrapping the URL parse and returning null (treated as “missing config”) so the user sees webdavMissingConfig instead:
- return {
- baseUrl,
- username: webdavUsernameInput.value,
- password: webdavPasswordInput.value,
- requestOrigin: new URL(baseUrl).origin + "/*"
- };
+ try {
+ return {
+ baseUrl,
+ username: webdavUsernameInput.value,
+ password: webdavPasswordInput.value,
+ requestOrigin: new URL(baseUrl).origin + "/*"
+ };
+ } catch {
+ return null;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function getWebDAVConfig() { | |
| const baseUrl = webdavUrlInput.value.trim().replace(/\/+$/, ""); | |
| if (!baseUrl) { | |
| return null; | |
| } | |
| store.clear(); | |
| const entries = Object.entries(data); | |
| let pending = entries.length; | |
| return { | |
| baseUrl, | |
| username: webdavUsernameInput.value, | |
| password: webdavPasswordInput.value, | |
| requestOrigin: new URL(baseUrl).origin + "/*" | |
| }; | |
| } | |
| function getWebDAVConfig() { | |
| const baseUrl = webdavUrlInput.value.trim().replace(/\/+$/, ""); | |
| if (!baseUrl) { | |
| return null; | |
| } | |
| try { | |
| return { | |
| baseUrl, | |
| username: webdavUsernameInput.value, | |
| password: webdavPasswordInput.value, | |
| requestOrigin: new URL(baseUrl).origin + "/*" | |
| }; | |
| } catch { | |
| return null; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/backup-restore.js` around lines 267 - 279, getWebDAVConfig currently
calls new URL(baseUrl).origin which can throw for malformed user input and
surface as an upload failure; modify getWebDAVConfig to validate/parse baseUrl
inside a try/catch (or use a safe absolute-URL check) and if parsing fails
return null so callers (e.g., uploadSnapshotToWebDAV) treat it as missing
config; ensure the returned object still contains baseUrl, username, password
and a safe requestOrigin only when URL parsing succeeds.
| async function exportObjectStore(db, storeName) { | ||
| return new Promise((resolve, reject) => { | ||
| const transaction = db.transaction(storeName, "readonly"); | ||
| const store = transaction.objectStore(storeName); | ||
| const data = {}; | ||
|
|
||
| store.getAllKeys().onsuccess = (keysEvent) => { | ||
| const keys = keysEvent.target.result; | ||
|
|
||
| if (!keys.length) { | ||
| resolve({}); | ||
| return; | ||
| } | ||
|
|
||
| let pending = keys.length; | ||
| keys.forEach((key) => { | ||
| store.get(key).onsuccess = async (getEvent) => { | ||
| try { | ||
| data[key] = await serializeIndexedDBValue(getEvent.target.result); | ||
| pending -= 1; | ||
| if (pending === 0) { | ||
| resolve(data); | ||
| } | ||
| } catch (error) { | ||
| reject(error); | ||
| } | ||
| }; | ||
| }); | ||
| }; | ||
|
|
||
| transaction.onerror = () => reject(transaction.error); | ||
| }); | ||
| } |
There was a problem hiding this comment.
exportObjectStore can hang if getAllKeys fails.
Only store.getAllKeys().onsuccess and transaction.onerror are handled. If getAllKeys itself fails (request-level error, e.g., store not found after an upgrade), neither handler fires and the outer await never resolves/rejects. Similarly store.get(key) has no onerror.
- store.getAllKeys().onsuccess = (keysEvent) => {
+ const keysRequest = store.getAllKeys();
+ keysRequest.onerror = () => reject(keysRequest.error);
+ keysRequest.onsuccess = (keysEvent) => {
...
- store.get(key).onsuccess = async (getEvent) => {
+ const getRequest = store.get(key);
+ getRequest.onerror = () => reject(getRequest.error);
+ getRequest.onsuccess = async (getEvent) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/snapshot-service.js` around lines 119 - 151, The exportObjectStore
function can hang because store.getAllKeys() and store.get(key) lack error
handlers; add request.onerror handlers for the getAllKeys() request to reject
with its error, and for each store.get(key) to reject on failure (referencing
exportObjectStore, store.getAllKeys, and the inner store.get loop), ensuring
pending is decremented/short-circuited appropriately if needed; keep the
existing transaction.onerror but ensure any rejection clears outstanding
callbacks so the returned Promise always resolves or rejects, and still await
serializeIndexedDBValue(result) inside the successful get handler.
| .cloudMethodInlinePicker { | ||
| background-color: var(--lightColor-blue); | ||
| border-radius: 22px; | ||
| padding: 16px; | ||
| display: none; | ||
| box-shadow: 0 18px 48px rgba(0, 0, 0, 0.08); | ||
| } |
There was a problem hiding this comment.
var(--lightColor-blue) is not defined anywhere.
Only --bg-color-blue, --accentLightTint-blue, --darkerColor-blue, --darkColor-blue, --textColorDark-blue, and --whitishColor-blue are declared in :root. --lightColor-blue does not exist, so background-color resolves to unset/transparent and .cloudMethodInlinePicker renders with no fill when active. If this class isn’t exercised in the current UI flow it may be going unnoticed — please either point it at the correct token or remove the rule.
- background-color: var(--lightColor-blue);
+ background-color: var(--accentLightTint-blue);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .cloudMethodInlinePicker { | |
| background-color: var(--lightColor-blue); | |
| border-radius: 22px; | |
| padding: 16px; | |
| display: none; | |
| box-shadow: 0 18px 48px rgba(0, 0, 0, 0.08); | |
| } | |
| .cloudMethodInlinePicker { | |
| background-color: var(--accentLightTint-blue); | |
| border-radius: 22px; | |
| padding: 16px; | |
| display: none; | |
| box-shadow: 0 18px 48px rgba(0, 0, 0, 0.08); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@style.css` around lines 4073 - 4079, The .cloudMethodInlinePicker rule uses
an undefined CSS variable --lightColor-blue so its background becomes
transparent; update the background-color to a valid token (e.g. --bg-color-blue
or --accentLightTint-blue) or remove the background-color declaration if no fill
is desired; modify the .cloudMethodInlinePicker selector in the stylesheet to
reference the chosen existing variable (instead of --lightColor-blue) and verify
the visual result in the UI.
feat: support WebDAV backup
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
locales/en.js (1)
209-221:⚠️ Potential issue | 🟠 MajorRemove the stale duplicate WebDAV locale block.
Lines 209-221 are overwritten by the newer Cloud Backup/WebDAV entries at Lines 225-244, and Biome correctly flags these as duplicate object keys. Keeping both blocks also leaves obsolete wording like
webdavFileNamePlaceholderand “URL and file name” in the source.🧹 Proposed cleanup
- "webdavTitle": "WebDAV Sync", - "webdavBackupText": "Backup to WebDAV", - "webdavRestoreText": "Restore from WebDAV", - "webdavUrlPlaceholder": "https://example.com/remote.php/dav/files/username/backups", - "webdavFileNamePlaceholder": "mynt-backup.json", - "webdavUsernamePlaceholder": "Username (optional)", - "webdavPasswordPlaceholder": "Password or app password (optional)", - "webdavMissingConfig": "Please fill in the WebDAV URL and file name first.", - "webdavPermissionDenied": "Host permission was not granted for this WebDAV server.", - "webdavBackupCompleted": "Backup uploaded to WebDAV successfully!", - "webdavDownloadFailed": "Failed to download backup from WebDAV: ", - "webdavUploadFailed": "Failed to upload backup from WebDAV: ", - "webdavRestoreConfirm": "Restore settings from WebDAV? Your current local data will be overwritten.", "deleteBookmark": "Are you sure you want to delete the bookmark \"{title}\"?", // Do not translate {title} "UnsupportedBrowser": "Bookmarks are not supported in your browser.", "resetShortcutsPrompt": "All saved shortcuts will be deleted and reset to default. Do you want to continue?",Also applies to: 225-244
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@locales/en.js` around lines 209 - 221, Remove the stale duplicate WebDAV locale block (the early set of keys including webdavTitle, webdavBackupText, webdavRestoreText, webdavUrlPlaceholder, webdavFileNamePlaceholder, webdavUsernamePlaceholder, webdavPasswordPlaceholder, webdavMissingConfig, webdavPermissionDenied, webdavBackupCompleted, webdavDownloadFailed, webdavUploadFailed, webdavRestoreConfirm) so only the newer Cloud Backup/WebDAV entries remain; ensure you keep the canonical keyset (the later block around webdav... keys) and delete the older duplicate to avoid duplicate object keys and obsolete wording like "file name" and webdavFileNamePlaceholder.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Line 233: Update the flag image tags in the README table to include alt
attributes and correct the Swedish flag URL: change the Swedish flag src from
flagcdn.com/sv.svg to flagcdn.com/se.svg and add alt text for each row's img tag
(e.g. alt="Hungarian flag" for the "Hungarian - Magyar" row, alt="Swedish flag"
for the "Swedish" row, and alt="Ukrainian flag" for the "Ukrainian" row) so the
Hungarian, Swedish, and Ukrainian table rows have proper alt attributes and the
correct Swedish image source.
---
Duplicate comments:
In `@locales/en.js`:
- Around line 209-221: Remove the stale duplicate WebDAV locale block (the early
set of keys including webdavTitle, webdavBackupText, webdavRestoreText,
webdavUrlPlaceholder, webdavFileNamePlaceholder, webdavUsernamePlaceholder,
webdavPasswordPlaceholder, webdavMissingConfig, webdavPermissionDenied,
webdavBackupCompleted, webdavDownloadFailed, webdavUploadFailed,
webdavRestoreConfirm) so only the newer Cloud Backup/WebDAV entries remain;
ensure you keep the canonical keyset (the later block around webdav... keys) and
delete the older duplicate to avoid duplicate object keys and obsolete wording
like "file name" and webdavFileNamePlaceholder.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc6442e3-b3a7-4920-bfe7-0d86113d53d4
📒 Files selected for processing (18)
.github/pull_request_template.mdCHANGELOG.mdREADME.mdindex.htmllocales/bn.jslocales/cs.jslocales/en.jslocales/hi.jslocales/hu.jslocales/sv.jslocales/uk.jsmanifest(firefox).jsonmanifest.jsonscripts/clock.jsscripts/languages.jsscripts/search.jsscripts/weather.jstools/languagesAnalysis.html
✅ Files skipped from review due to trivial changes (7)
- .github/pull_request_template.md
- manifest(firefox).json
- scripts/clock.js
- locales/sv.js
- locales/hi.js
- locales/uk.js
- locales/cs.js
🚧 Files skipped from review as they are similar to previous changes (5)
- CHANGELOG.md
- scripts/languages.js
- locales/bn.js
- manifest.json
- index.html
📌 Description
This PR adds WebDAV backup support to MYNT and refines the overall cloud backup flow.
What’s included
downloadssupport for backup file exportWhy
This allows users to back up and restore their MYNT settings through their own WebDAV storage instead of relying only on manual local JSON export/import.
🎨 Visual Changes (Screenshots / Videos)
🔗 Related Issues
✅ Checklist
Overview
This PR introduces WebDAV backup and restore functionality to MaterialYouNewTab, enabling users to securely back up their settings to their own WebDAV storage servers. It establishes a unified snapshot architecture for managing backup/restore operations and expands localization support to 32 languages, including the newly added Swedish.
Core Functionality
Snapshot Service (
scripts/snapshot-service.js)localStorageand selected IndexedDB stores (currentlyImageDB→backgroundImages)WebDAV Cloud Backup (
scripts/backup-restore.js)chrome.permissions/browser.permissionsAPIs*_latest.jsonpointer files for quick restore discoverySettings UI (
index.html,style.css)Localization & Language Support
locales/sv.js): Full translation set with regional formattingcloudBackupSectionTitle,webdavTitle,webdavUrlPlaceholder,saveBackupConfig,webdavMissingConfig,webdavPermissionDenied, etc.)scripts/languages.js): Swedish added to supported language tables; localized date formatting and New Tab title support addedManifest & Permissions
"downloads"to required permissions (for backup file exports)http://*/*andhttps://*/*(for user-provided WebDAV servers)Related Changes
translations,menuWidths, decimal separators, clock date formatting, weather humidity sliders)tools/languagesAnalysis.html): Updated to include Swedish locale validationdraggable="false")Technical Architecture
The PR refactors backup logic from inline implementations to a service-based model where:
snapshotServicehandles snapshot creation, validation, and restorationbackup-restore.jsorchestrates local exports and WebDAV cloud operationsloadWebDAVSettings(),setupAutoBackupScheduler(),applyStorageTexts())Browser Compatibility
Changes are compatible with both Chrome (via
chrome.permissions) and Firefox (viabrowser.permissions) with conditional API usage patterns maintained.